﻿using System;
using System.Threading;

namespace Robotics.Paralelism
{
	/// <summary>
	/// Severs as base class to implement a Filter
	/// </summary>
	/// <typeparam name="TInput">The input data type</typeparam>
	/// <typeparam name="TOutput">The output data type</typeparam>
	public abstract class Filter<TInput, TOutput> : IFilter
	{
		#region Variables

		/// <summary>
		/// The pipe for data input
		/// </summary>
		private IPipe<TInput> inputPipe;

		/// <summary>
		/// Indicates whether the Filter object will execute
		/// or not in a background thread while running asynchronously
		/// </summary>
		private bool runInBackground;

		/// <summary>
		/// Indicating whether the Filter is running asynchronously
		/// </summary>
		private bool running;

		/// <summary>
		/// Object used to synchronize the acces to several methods
		/// </summary>
		private object oLock;

		/// <summary>
		/// The pipe for data output
		/// </summary>
		private IPipe<TOutput> outputPipe;

		#endregion

		#region Constructors

		/// <summary>
		/// Initializes a new instance of Filter
		/// </summary>
		public Filter()
		{
			oLock = new Object();
			running = false;
		}

		#endregion

		#region Events
		#endregion

		#region Properties

		/// <summary>
		/// Gets or sets the pipe for data input
		/// </summary>
		public IPipe<TInput> InputPipe
		{
			get { return this.inputPipe; }
			set
			{
				Monitor.Enter(oLock);
				if (running)
				{
					Monitor.PulseAll(oLock);
					Monitor.Exit(oLock);
					return;
				}
				this.inputPipe = value;
				Monitor.PulseAll(oLock);
				Monitor.Exit(oLock);
			}
		}

		/// <summary>
		/// Gets the pipe input data type
		/// </summary>
		public Type InputType { get { return typeof(TInput); } }

		/// <summary>
		/// Gets a value indicating whether the Filter is running asynchronously
		/// </summary>
		public bool IsRunning { get { return this.running; } }

		/// <summary>
		/// Gets or sets the pipe for data output
		/// </summary>
		public IPipe<TOutput> OutputPipe
		{
			get { return this.outputPipe; }
			set
			{
				Monitor.Enter(oLock);
				if (running)
				{
					Monitor.PulseAll(oLock);
					Monitor.Exit(oLock);
					return;
				}
				this.outputPipe = value;
				Monitor.PulseAll(oLock);
				Monitor.Exit(oLock);
			}
		}

		/// <summary>
		/// Gets the Filter output data type
		/// </summary>
		public Type OutputType { get { return typeof(TOutput); } }

		/// <summary>
		/// Gets or sets a value indicating whether the Filter object will execute
		/// or not in a background thread while running asynchronously
		/// </summary>
		public bool RunInBackground
		{
			get { return runInBackground; }
			set
			{
				Monitor.Enter(oLock);
				if (running)
				{
					Monitor.PulseAll(oLock);
					Monitor.Exit(oLock);
					return;
				}
				runInBackground = value;
				Monitor.PulseAll(oLock);
				Monitor.Exit(oLock);
			}
		}

		#endregion

		#region Methodos

		/// <summary>
		/// Executes the Filter task over the data provided by the input pipe
		/// and writes the result to the output pipe.
		/// Is est, executes the operation: OutputPipe.Write(FilterTask(InputPipe.Read()));
		/// </summary>
		public virtual void FilterNext()
		{
			Monitor.Enter(oLock);
			outputPipe.Write(FilterTask(inputPipe.Read()));
			Monitor.PulseAll(oLock);
			Monitor.Exit(oLock);
		}

		/// <summary>
		/// When overriden in a derived task, executes the filter
		/// </summary>
		/// <param name="input">The input data for the filter</param>
		/// <returns>The output data generated by the filter</returns>
		protected abstract TOutput FilterTask(TInput input);

		#endregion
	}
}
